﻿using NHibernate;
using NHibernate.Tool.hbm2ddl;
using NHibernate.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Utility.Global.Exceptions;

namespace Utility.Persistence_Service.Implementations
{
    class UnitOfWorkImpl : UnitOfWork
    {
        /// <summary>
        /// Referenz zur SessionFactory von NHibernate.
        /// </summary>
        private readonly ISessionFactory sessionFactory;

        /// <summary>
        /// NHibernates Session Objekt für Queries.
        /// </summary>
        private ISession session = null;

        /// <summary>
        /// Referenz zur aktuelle laufenden Transaktion.
        /// </summary>
        private ITransaction transaction = null;

        /// <summary>
        /// Rollback beim nächsten Dispose?
        /// </summary>
        private bool rollbackOnDispose = false;

        internal UnitOfWorkImpl(ISessionFactory sessionFactory)
        {
            if (sessionFactory == null)
            {
                throw new TechnicalProblemException("UnitOfWork benötigt eine SessionFactory.");
            }
            this.sessionFactory = sessionFactory;
            this.OpenSession();
            this.BeginTransaction();
        }

        /// <summary>
        /// Öffnen eine Verbindung zur Datenbank.
        /// </summary>
        private void OpenSession()
        {
            session = sessionFactory.OpenSession();
            session.FlushMode = FlushMode.Commit;
        }

        /// <summary>
        /// Startet eine Transaktion.
        /// </summary>
        private void BeginTransaction()
        {
            transaction = session.BeginTransaction();
        }

        /// <summary>
        /// Commited die aktive Transaktion.
        /// </summary>
        private void Commit()
        {
            if (!transaction.IsActive)
            {
                throw new InvalidOperationException("Keine Transaktion aktiv!");
            }
            transaction.Commit();
        }

        /// <summary>
        /// Schließt die aktive Datenbank-Verbindung.
        /// </summary>
        private void CloseSession()
        {
            if (!session.IsOpen)
            {
                throw new TechnicalProblemException("Keine Session aktiv!");
            }
            session.Flush();
            session.Clear();
            session.Close();
        }

        /// <summary>
        /// Commited oder Rollbacked die aktive Transaktion und schließt die aktive Datenbank-Verbindung.
        /// Anschließen wird eine neue Transaktion begonnen mit neuer Datenbank-Verbindung
        /// </summary>
        public void Reset()
        {
            if (!transaction.IsActive)
            {
                throw new TechnicalProblemException("Keine Transaktion aktiv!");
            }
            this.Dispose();
            this.OpenSession();
            this.BeginTransaction();
        }

        /// <summary>
        /// Commited oder Rollbacked die aktive Transaktion.
        /// Anschließen wird eine neue Transaktion begonnen.
        /// </summary>
        public void ResetTransactionOnly()
        {
            if (!transaction.IsActive)
            {
                throw new TechnicalProblemException("Keine Transaktion aktiv!");
            }
            if (rollbackOnDispose)
            {
                transaction.Rollback();
            }
            else
            {
                transaction.Commit();
            }
            this.BeginTransaction();
        }

        /// <summary>
        /// Rollbacked die aktive Transaktion.
        /// </summary>
        private void Rollback()
        {
            if (transaction.IsActive)
            {
                transaction.Rollback();
            }
        }

        /// <summary>
        /// Rollbacked die aktive Transaktion beim nächsten Dispose.
        /// </summary>
        public void RollbackOnDispose()
        {
            this.rollbackOnDispose = true;
        }

        /// <summary>
        /// Der Consumer eines Objekts kann diese Methode aufrufen, wenn das Objekt 
        /// nicht mehr benötigt wird. (using)
        /// </summary>
        public void Dispose()
        {
            if (session.IsOpen)
            {
                if (rollbackOnDispose)
                {
                    transaction.Rollback();
                }
                else
                {
                    transaction.Commit();
                }
                this.CloseSession();
            }
        }
        
        public IQueryable<T> Query<T>() where T : class
        {
            return this.session.Query<T>();
        }

        public T FindById<T, V>(V id) where T : class
        {
            return this.session.Get<T>(id);
        }

        public T Create<T>(T entity) where T : class
        {
            this.session.Save(entity);
            return entity;
        }

        public T CreateOrUpdate<T>(T entity) where T : class
        {
            this.session.SaveOrUpdate(entity);
            return entity;
        }

        public T Update<T>(T entity) where T : class
        {
            this.session.Update(entity);
            return entity;
        }

        public void Delete<T>(T entity) where T : class
        {
            this.session.Delete(entity);
        }

        public IList<T> FindAll<T>() where T : class
        {
            return this.session.CreateCriteria<T>().List<T>();
        }
    }
}
